package com.google.code.ojar.sendtemplatemail.app.utils;

import java.beans.IntrospectionException;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.Writer;
import java.util.Properties;

import org.apache.commons.betwixt.io.BeanReader;
import org.apache.commons.betwixt.io.BeanWriter;
import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
import org.jasypt.properties.EncryptableProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;

import com.google.code.ojar.sendtemplatemail.app.dto.NotSendMail;
import com.google.code.ojar.sendtemplatemail.app.exception.AppException;
import com.google.code.ojar.sendtemplatemail.app.exception.MissingConfigurationException;
import com.google.code.ojar.sendtemplatemail.app.library.SendMailLibrary;
import com.google.code.ojar.sendtemplatemail.app.msggenerators.MsgGenerator;


/**
 * Class that holds the utils methods used in the app
 * @author ottoabreu
 *
 */
public class SendMailUtils {
	//logger
	private static final Logger logger = LoggerFactory.getLogger(SendMailUtils.class);
	/**
	 * Encryption tokens 
	 */
	private static final String ENCRYPT_INI_TOKEN="ENC(";
	private static final String ENCRYPT_END_TOKEN=")";

	/**
	 * method that generate the encriptor 
	 * @return {@link StandardPBEStringEncryptor}
	 */
	private static StandardPBEStringEncryptor getEncriptor(){
		StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
		encryptor.setPassword("m3g46r0up53ndm4il-._PBEWithMD5AndDES"); 
		encryptor.setAlgorithm("PBEWithMD5AndDES"); 
		return encryptor;
	}



	/**
	 * method that gets a property value from a properties file
	 * @param resourceName String with the resource name
	 * @param resourcekey String with the key present in the properties file
	 * @return String
	 */
	public static String getPropertiesString(String resourceName, String resourcekey){
		String value ="";
		InputStream is = null;
		try {
			is = SendMailUtils.class.getClassLoader().getResourceAsStream(resourceName);
			logger.debug("resourcekey:"+resourcekey);	
			Properties props = new Properties();
			props.load(is);
			value = props.getProperty(resourcekey);
			logger.debug("value al inicio:"+value);
			//verifico si el valor esta cifrado ( posee el ENC() )
			if(value != null && value.startsWith(ENCRYPT_INI_TOKEN)){
				logger.debug("esta cifrado");
				//Utilizo EncryptableProperties para decifrarlo
				EncryptableProperties propsEnc = new EncryptableProperties(SendMailUtils.getEncriptor());
				propsEnc.putAll(props);
				value = propsEnc.getProperty(resourcekey);

			}else{
				//no esta cifrado y verifico si es data sensible para cifrarse
				String mode = props.getProperty(SendMailLibrary.PROPERTY_KEY_MODE);
				logger.debug("mode:"+mode);
				// si no es mode desa ( desarrollo) y si el key no es correspondiente a alguno de los parametros  de conex
				if((mode !=null && !mode.equalsIgnoreCase(SendMailLibrary.PROPERTY_VALUE_MODE_DESA)) 
						&& (resourcekey.equals(SendMailLibrary.PROPERTY_KEY_MAIL_USERPW)
								||resourcekey.equals(SendMailLibrary.PROPERTY_KEY_MAIL_USER))){
					logger.debug("hay que cifrar");
					EncryptableProperties propsEnc = new EncryptableProperties(SendMailUtils.getEncriptor());
					propsEnc.putAll(props);
					SendMailUtils.encryptPropFile(propsEnc,resourceName);
				}else{
					if(resourcekey.equals(SendMailLibrary.PROPERTY_KEY_MAIL_USERPW) 
							|| resourcekey.equals(SendMailLibrary.PROPERTY_KEY_MAIL_USER)){
						//notifico que esta colocado el modo desarrollo
						logger.info("Properties file will not be encrypted because mode desa is on, remove or change the value from "+SendMailLibrary.PROPERTY_KEY_MODE);
					}

				}
			}
		} catch (IOException e) {
			logger.error("Missing properties file: "+resourceName);
			throw new AppException(AppException.RESOURCE_NOT_FOUND_ERROR+" "+resourceName);
		}finally{
			try {
				if(is != null){
					is.close();
				}
			} catch (IOException e) {
				logger.error("cant close resource "+resourceName);
			}
		}

		return value;

	}
	/**
	 * Method that encrypt the physical property file
	 * @param props Properties to be encrypted
	 */
	private static void encryptPropFile(Properties props, String resourceName){

		String path = props.getProperty(SendMailLibrary.PROPERTY_KEY_FILEPATH);
		FileOutputStream fos = null;

		//verifico si se establecio el path del property para asi poder cifrarlo
		if(path!= null){
			//obtengo las propiedades a cifrar

			String user = props.getProperty(SendMailLibrary.PROPERTY_KEY_MAIL_USER);
			String pass = props.getProperty(SendMailLibrary.PROPERTY_KEY_MAIL_USERPW);

			props.setProperty(SendMailLibrary.PROPERTY_KEY_MAIL_USER, ENCRYPT_INI_TOKEN+SendMailUtils.getEncriptor().encrypt(user)+ENCRYPT_END_TOKEN);
			props.setProperty(SendMailLibrary.PROPERTY_KEY_MAIL_USERPW, ENCRYPT_INI_TOKEN+SendMailUtils.getEncriptor().encrypt(pass)+ENCRYPT_END_TOKEN);


			try {
				//creo el stream del archivo
				fos = new FileOutputStream(path+"/"+resourceName);
				props.store(fos, "encrypted from the original file");

			} catch (FileNotFoundException e) {
				logger.error("Can't encrypt property file, wrong path defined in:"+ SendMailLibrary.PROPERTY_KEY_FILEPATH);
			} catch (IOException e) {
				logger.error("Can't encrypt property file IOException",e);
			}catch(Exception e){
				logger.error("Can't encrypt property file general Exception",e);
			}finally{
				if(fos != null){
					try {
						fos.close();
					} catch (IOException e) {
						logger.error("cant close resource ");
					}
				}
			}
		}else{
			logger.error("Can't encrypt property file, missing key "+ SendMailLibrary.PROPERTY_KEY_FILEPATH+" please especify the path to encrypt");
		}
		logger.debug("props encripted");
	}
	/**
	 * Generate a new instance of a given class
	 * @param instance Class<?>
	 * @return Object
	 * @throws AppException
	 */
	public static Object newObjectClass(Class<?>  instance){

		Object newobjectType  = null;
		
		try {
			//instancio el nuevo objeto a partir de la clase     
			newobjectType = instance.newInstance();                           

		} catch (InstantiationException ie) {
			logger.error(AppException.NEW_OBJECT_EXCEPTION + ie,ie);
			throw new  AppException(AppException.NEW_OBJECT_EXCEPTION + ie,ie);

		} catch (IllegalAccessException iae) {
			logger.error(AppException.NEW_OBJECT_EXCEPTION + iae,iae);
			throw new AppException(AppException.NEW_OBJECT_EXCEPTION + iae,iae);

		}

		return newobjectType;
	}

	/**
	 * method that returns a Class object 
	 * @param classname
	 * @param classLoader
	 * @return Class<?>
	 * @throws ClassNotFoundException 
	 */
	@SuppressWarnings("static-access")
	public static Class<?> getClass(String classname, ClassLoader classLoader) throws ClassNotFoundException {
		Class<?> classObj = null;
		try {
			classObj = classLoader.loadClass(classname);

		} catch (ClassNotFoundException e) {
			try {
				classLoader.getParent().loadClass(classname);
			} catch (ClassNotFoundException e1) {

				classLoader.getSystemClassLoader().loadClass(classname);
			}
		}
		return classObj;
	}


	public static void generateNotSendFragmentXML(NotSendMail mail, Writer writer) throws IOException, SAXException, IntrospectionException{
	
		BeanWriter beanWriter = new BeanWriter(writer);
		beanWriter.getXMLIntrospector().getConfiguration().setAttributesForPrimitives(false);
		beanWriter.getBindingConfiguration().setMapIDs(false);
		beanWriter.enablePrettyPrint();
		beanWriter.write("MailNotSendBean",mail); 
	}
	
	public static NotSendMail generateNotSendedMailOb(String xml) throws IntrospectionException, IOException, SAXException{
	
        BeanReader beanReader  = new BeanReader();
        beanReader.getXMLIntrospector().getConfiguration().setAttributesForPrimitives(false);
        beanReader.getBindingConfiguration().setMapIDs(false);
        beanReader.registerBeanClass("MailNotSendBean", NotSendMail.class);
        StringReader xmlReader = new StringReader(xml);
        NotSendMail mail = (NotSendMail) beanReader.parse(xmlReader);
        return mail;
	}
	
	public static MsgGenerator generatorFactory(String generatorName,ClassLoader classLoader) throws MissingConfigurationException{
		MsgGenerator gen = null; 
		String className = SendMailUtils.getPropertiesString(
					SendMailLibrary.PROPERTY_GENERATORS_FILE_NAME,
					SendMailLibrary.PROPERTY_GENERATORS_KEY_CLASSNAME+generatorName);
		
		if(className != null && !className.equals("")){
			@SuppressWarnings("rawtypes")
			Class instance;
			try {
				instance = SendMailUtils.getClass(className, classLoader);
			} catch (ClassNotFoundException e) {
				logger.error(AppException.CLASS_NOT_FOUND_EXCEPTION+className,e);
				throw new AppException(AppException.CLASS_NOT_FOUND_EXCEPTION+className,e);
			}
			gen = (MsgGenerator)SendMailUtils.newObjectClass(instance);
			gen.setGeneratorName(generatorName);
			gen.setTemplateFolder();
		 }else{
			 logger.error(MissingConfigurationException.MISSING_GENERATOR_NAME);
			 throw new MissingConfigurationException(MissingConfigurationException.MISSING_GENERATOR_NAME+SendMailLibrary.PROPERTY_GENERATORS_KEY_CLASSNAME+generatorName);
		 }
		 logger.info("Message generator: "+generatorName+" Loaded");
		 return gen;
	}
}
